/**
 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "plugins/ecmascript/runtime/ecma_vm.h"
#include <mem/gc/gc_types.h>
#include "include/mem/panda_containers.h"
#include "include/mem/panda_string.h"
#include "plugins/ecmascript/runtime/bridge.h"

#include "js_tagged_value.h"
#include "plugins/ecmascript/runtime/base/string_helper.h"
#include "plugins/ecmascript/runtime/builtins.h"
#include "plugins/ecmascript/runtime/builtins/builtins_regexp.h"
#include "plugins/ecmascript/runtime/class_linker/panda_file_translator.h"
#include "plugins/ecmascript/runtime/class_linker/program_object-inl.h"
#include "plugins/ecmascript/runtime/ecma_module.h"
#include "plugins/ecmascript/runtime/ecma_string_table.h"
#include "plugins/ecmascript/runtime/global_dictionary.h"
#include "plugins/ecmascript/runtime/global_env.h"
#include "plugins/ecmascript/runtime/global_env_constants-inl.h"
#include "plugins/ecmascript/runtime/global_env_constants.h"
#include "plugins/ecmascript/runtime/internal_call_params.h"
#include "plugins/ecmascript/runtime/interpreter/js_frame-inl.h"
#include "plugins/ecmascript/runtime/jobs/micro_job_queue.h"
#include "plugins/ecmascript/runtime/js_arraybuffer.h"
#include "plugins/ecmascript/runtime/js_for_in_iterator.h"
#include "plugins/ecmascript/runtime/js_invoker.h"
#include "plugins/ecmascript/runtime/js_native_pointer.h"
#include "plugins/ecmascript/runtime/js_thread.h"
#include "plugins/ecmascript/runtime/mem/object_xray-inl.h"
#include "plugins/ecmascript/runtime/object_factory.h"
#include "plugins/ecmascript/runtime/ecma_profiling.h"
#include "plugins/ecmascript/runtime/regexp/regexp_parser_cache.h"
#include "plugins/ecmascript/runtime/runtime_call_id.h"
#include "plugins/ecmascript/runtime/symbol_table.h"
#include "plugins/ecmascript/runtime/tagged_array-inl.h"
#include "plugins/ecmascript/runtime/tagged_dictionary.h"
#include "plugins/ecmascript/runtime/tagged_queue-inl.h"
#include "plugins/ecmascript/runtime/tagged_queue.h"
#include "plugins/ecmascript/runtime/template_map.h"
#include "plugins/ecmascript/runtime/vmstat/runtime_stat.h"
#include "plugins/ecmascript/compiler/ecmascript_extensions/thread_environment_api.h"
#include "plugins/ecmascript/runtime/ecma_class_linker_extension.h"
#include "plugins/ecmascript/runtime/mem/ecma_reference_processor.h"
#include "plugins/ecmascript/runtime/js_finalization_registry.h"
#include "include/runtime_notification.h"
#include "libpandafile/file.h"

#include "runtime/include/profiling_gen.h"

#include "runtime/compiler.h"
#include "runtime/include/thread_scopes.h"
#include "runtime/mem/gc/gc_root.h"
#include "runtime/mem/gc/reference-processor/empty_reference_processor.h"
#include "runtime/mem/lock_config_helper.h"
#include "runtime/mem/object_helpers.h"
#include "plugins/ecmascript/runtime/compiler/ecmascript_runtime_interface.h"
#include "plugins/ecmascript/runtime/ecma_compiler.h"

#include "handle_scope-inl.h"
#include "include/coretypes/native_pointer.h"
#include "include/runtime.h"
#include "include/method.h"
#include "mem/internal_allocator.h"
#include "trace/trace.h"
#include "ecmastdlib_inline_gen.h"
#include "ir-dyn-base-types.h"
#include "ic/profile_type_info.h"
#include "serializer/serializer.h"

namespace ark::ecmascript {
// NOLINTNEXTLINE(fuchsia-statically-constructed-objects)
static const std::string_view ENTRY_POINTER = "_GLOBAL::func_main_0";
// NOLINTNEXTLINE(fuchsia-statically-constructed-objects)
static const std::string_view ENTRY_METHOD_POINTER = "func_main_0";
JSRuntimeOptions EcmaVM::options_;  // NOLINT(fuchsia-statically-constructed-objects)

// Create MemoryManager by RuntimeOptions
static mem::MemoryManager *CreateMM(const LanguageContext &ctx, mem::InternalAllocatorPtr internalAllocator,
                                    const RuntimeOptions &options)
{
    mem::MemoryManager::HeapOptions heapOptions {
        nullptr,                                      // is_object_finalizeble_func
        nullptr,                                      // register_finalize_reference_func
        options.GetMaxGlobalRefSize(),                // max_global_ref_size
        options.IsGlobalReferenceSizeCheckEnabled(),  // is_global_reference_size_check_enabled
        MT_MODE_SINGLE,                               // multithreading mode
        options.IsUseTlabForAllocations(),            // is_use_tlab_for_allocations
        options.IsStartAsZygote(),                    // is_start_as_zygote
    };

    mem::GCTriggerConfig gcTriggerConfig(options, panda_file::SourceLang::ECMASCRIPT);

    mem::GCSettings gcSettings(options, panda_file::SourceLang::ECMASCRIPT);

    mem::GCType gcType = Runtime::GetGCType(options, panda_file::SourceLang::ECMASCRIPT);
    ASSERT(gcType != mem::GCType::GEN_GC);

    return mem::MemoryManager::Create(ctx, internalAllocator, gcType, gcSettings, gcTriggerConfig, heapOptions);
}

/* static */
EcmaVM *EcmaVM::Create(const JSRuntimeOptions &options)
{
    auto runtime = Runtime::GetCurrent();
    auto vm = runtime->GetInternalAllocator()->New<EcmaVM>(options);
    if (UNLIKELY(vm == nullptr)) {
        LOG_ECMA(ERROR) << "Failed to create jsvm";
        return nullptr;
    }
    vm->InitializeGC();
    auto jsThread = JSThread::Create(runtime, vm);
    vm->thread_ = jsThread;
    vm->threadManager_->SetMainThread(jsThread);
    if (!vm->Initialize()) {
        LOG_ECMA(ERROR) << "Failed to initialize jsvm";
        runtime->GetInternalAllocator()->Delete(vm);
        return nullptr;
    }
    return vm;
}

// static
bool EcmaVM::Destroy(PandaVM *vm)
{
    if (vm != nullptr) {
        vm->SaveProfileInfo();
        vm->UninitializeThreads();
        vm->StopGC();
        auto runtime = Runtime::GetCurrent();
        runtime->GetInternalAllocator()->Delete(vm);
        return true;
    }
    return false;
}

// static
Expected<EcmaVM *, PandaString> EcmaVM::Create(Runtime *runtime, const JSRuntimeOptions &options)
{
    EcmaVM *vm = runtime->GetInternalAllocator()->New<EcmaVM>(options);
    if (UNLIKELY(vm == nullptr)) {
        LOG_ECMA(ERROR) << "Failed to create jsvm";
        return nullptr;
    }
    vm->InitializeGC();
    auto jsThread = ecmascript::JSThread::Create(runtime, vm);
    vm->thread_ = jsThread;
    vm->threadManager_->SetMainThread(jsThread);
    return vm;
}

EcmaVM::EcmaVM(JSRuntimeOptions options) : stringTable_(new EcmaStringTable(this))
{
    auto runtime = Runtime::GetCurrent();
    options_ = std::move(options);
    icEnable_ = options_.IsIcEnable();
    optionalLogEnabled_ = options_.IsEnableOptionalLog();
    frameworkAbcFileName_ = options_.GetFrameworkAbcFile();

    auto allocator = runtime->GetInternalAllocator();
    threadManager_ = allocator->New<SingleThreadManager>();
    rendezvous_ = allocator->New<Rendezvous>(this);
    notificationManager_ = allocator->New<RuntimeNotificationManager>(runtime->GetInternalAllocator());
    notificationManager_->SetRendezvous(rendezvous_);
    InitializeRandomEngine();

    LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ECMASCRIPT);
    mm_ = CreateMM(ctx, Runtime::GetCurrent()->GetInternalAllocator(), options_);
    if (options_.IsReferenceProcessorEnable()) {
        ecmaReferenceProcessor_ = MakePandaUnique<ark::mem::ecmascript::EcmaReferenceProcessor>(this);
    } else {
        ecmaReferenceProcessor_ = MakePandaUnique<ark::mem::EmptyReferenceProcessor>();
    }

    isProfilingEnabled_ = options_.IsCompilerEnableJit() || options_.WasSetProfileOutput();

    if (isProfilingEnabled_) {
        callProfilingTable_ = allocator->New<EcmaCallProfilingTable>(options_.GetCallProfilingTableSize());
    }

    auto heapManager = mm_->GetHeapManager();
    auto internalAllocator = heapManager->GetInternalAllocator();
    runtimeIface_ = internalAllocator->New<EcmaRuntimeInterface>(this, internalAllocator);
    compiler_ = internalAllocator->New<EcmaCompiler>(heapManager->GetCodeAllocator(), internalAllocator, options_,
                                                     heapManager->GetMemStats(), runtimeIface_);
    SetFrameExtSize(ecmascript::JSExtFrame::GetExtSize());
}

void EcmaVM::LoadEcmaStdLib()
{
    if (GetNativeMethodWrapper() != nullptr) {
        // Ecmastdlib was loaded via parameters.
        return;
    }

    size_t fsize = 0;
    unsigned char const *data = ark::ecmascript::ecmastdlib_inline::GetEcmastdlibData(fsize);
    if (fsize == 0) {
        // Ecmastdlib is missing.
        LOG(FATAL, ECMASCRIPT) << "Ecmastdlib is missing!";
        return;
    }

    auto pf = panda_file::OpenPandaFileFromMemory(data, fsize);
    ASSERT(pf);
    Runtime::GetCurrent()->GetClassLinker()->AddPandaFile(std::move(pf));
}

bool EcmaVM::Initialize()
{
    ECMA_BYTRACE_NAME(BYTRACE_TAG_ARK, "EcmaVM::Initialize");
    auto runtime = Runtime::GetCurrent();

    auto globalConst = const_cast<GlobalEnvConstants *>(thread_->GlobalConstants());
    regExpParserCache_ = new RegExpParserCache();
    factory_ = runtime->GetInternalAllocator()->New<ObjectFactory>(thread_);
    if (UNLIKELY(factory_ == nullptr)) {
        LOG_ECMA(FATAL) << "alloc factory_ failed";
        UNREACHABLE();
    }

    LanguageContext ctx = runtime->GetLanguageContext(panda_file::SourceLang::ECMASCRIPT);
    EcmaClassLinkerExtension::Cast(runtime->GetClassLinker()->GetExtension(ctx))->InitClasses(this);

    LoadEcmaStdLib();
    if (!intrinsics::Initialize(ark::panda_file::SourceLang::ECMASCRIPT)) {
        LOG(ERROR, RUNTIME) << "Failed to initialize Ecma intrinsics";
        return false;
    }

    [[maybe_unused]] EcmaHandleScope scope(thread_);
    LOG_ECMA(DEBUG) << "EcmaVM::Initialize run builtins";

    JSHandle<JSHClass> dynClassClassHandle = factory_->NewEcmaDynClassClass(nullptr, JSHClass::SIZE, JSType::HCLASS);
    JSHClass *dynclass = reinterpret_cast<JSHClass *>(dynClassClassHandle.GetTaggedValue().GetTaggedObject());
    dynclass->SetClass(dynclass);
    dynclass->GetHClass()->SetNativeFieldMask(JSHClass::NATIVE_FIELDS_MASK);
    JSHandle<JSHClass> globalEnvClass = factory_->CreateDynClass<GlobalEnv>(*dynClassClassHandle, JSType::GLOBAL_ENV);

    JSHandle<GlobalEnv> globalEnvHandle = factory_->NewGlobalEnv(*globalEnvClass);
    globalEnv_ = globalEnvHandle.GetTaggedValue();

    // init global env
    globalConst->InitRootsClass(thread_, dynClassClassHandle);
    factory_->ObtainRootClass(GetGlobalEnv());
    globalConst->InitGlobalConstant(thread_);
    JSHandle<TaggedArray> globglobalEnvHandleArray = factory_->NewEmptyArray();
    globalEnvHandle->SetEmptyArray(thread_, globglobalEnvHandleArray);
    JSHandle<TaggedArray> globalEnvHandleWeakArray = factory_->NewEmptyArray(true);
    globalEnvHandle->SetEmptyWeakArray(thread_, globalEnvHandleWeakArray);
    auto layoutInfoHandle = factory_->CreateLayoutInfo(0);
    globalEnvHandle->SetEmptyLayoutInfo(thread_, layoutInfoHandle);
    auto symbolTableHandle = SymbolTable::Create(thread_);
    globalEnvHandle->SetRegisterSymbols(thread_, symbolTableHandle);
    JSTaggedValue emptyStr = thread_->GlobalConstants()->GetEmptyString();
    stringTable_->InternEmptyString(EcmaString::Cast(emptyStr.GetTaggedObject()));
    auto taggedQueueHandle = factory_->NewTaggedQueue(0);
    globalEnvHandle->SetEmptyTaggedQueue(thread_, taggedQueueHandle);
    auto templateMapHandle = TemplateMap::Create(thread_);
    globalEnvHandle->SetTemplateMap(thread_, templateMapHandle);
    auto symbolTable2Handle = SymbolTable::Create(GetJSThread());
    globalEnvHandle->SetRegisterSymbols(GetJSThread(), symbolTable2Handle);
    SetupRegExpResultCache();
    microJobQueue_ = factory_->NewMicroJobQueue().GetTaggedValue();

    {
        builtins::Builtins builtins;
        builtins.Initialize(globalEnvHandle, thread_);
    }

    thread_->SetGlobalObject(globalEnvHandle->GetGlobalObject());

    moduleManager_ = new ModuleManager(this);

    InitializeFinish();
    notificationManager_->VmStartEvent();
    notificationManager_->VmInitializationEvent(thread_->GetThreadId());
    return true;
}

void EcmaVM::InitializeEcmaScriptRunStat()
{
    // NOLINTNEXTLINE(modernize-avoid-c-arrays)
    static const char *runtimeCallerNames[] = {
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define INTERPRETER_CALLER_NAME(name) "InterPreter::" #name,
        INTERPRETER_CALLER_LIST(INTERPRETER_CALLER_NAME)  // NOLINTNEXTLINE(bugprone-suspicious-missing-comma)
#undef INTERPRETER_CALLER_NAME
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define BUILTINS_API_NAME(class, name) "BuiltinsApi::" #class "_" #name,
#define BUILTINS_MACRO_GEN_V BUILTINS_API_NAME
#include "plugins/ecmascript/runtime/builtins/generated/builtins_ids_gen.inl"
#undef BUILTINS_MACRO_GEN_V
        BUILTINS_API_LIST(BUILTINS_API_NAME)
#undef BUILTINS_API_NAME
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define ABSTRACT_OPERATION_NAME(class, name) "AbstractOperation::" #class "_" #name,
            ABSTRACT_OPERATION_LIST(ABSTRACT_OPERATION_NAME)
#undef ABSTRACT_OPERATION_NAME
    };
    static_assert(sizeof(runtimeCallerNames) == sizeof(const char *) * ecmascript::RUNTIME_CALLER_NUMBER,
                  "Invalid runtime caller number");
    auto allocator = Runtime::GetCurrent()->GetInternalAllocator();
    runtimeStat_ = allocator->New<EcmaRuntimeStat>(runtimeCallerNames, ecmascript::RUNTIME_CALLER_NUMBER);
    if (UNLIKELY(runtimeStat_ == nullptr)) {
        LOG_ECMA(FATAL) << "alloc runtime_stat_ failed";
        UNREACHABLE();
    }
}

void EcmaVM::SetRuntimeStatEnable(bool flag)
{
    if (flag) {
        if (runtimeStat_ == nullptr) {
            InitializeEcmaScriptRunStat();
        }
    } else {
        if (runtimeStatEnabled_) {
            runtimeStat_->Print();
            runtimeStat_->ResetAllCount();
        }
    }
    runtimeStatEnabled_ = flag;
}

bool EcmaVM::InitializeFinish()
{
    vmInitialized_ = true;
    return true;
}

void EcmaVM::SaveProfileInfo()
{
    if (GetOptions().WasSetProfileOutput()) {
        if (profilesMethods_.empty()) {
            LOG(WARNING, RUNTIME) << "Profiled methods list is empty";
            return;
        }

        EcmaProfileContainer proto;

        for (auto method : profilesMethods_) {
            auto methodName = method->GetFullName();

            if (method->GetProfilingVector() != nullptr) {
                panda_file::MethodDataAccessor mda(*method->GetPandaFile(), method->GetFileId());
                auto profileData = method->GetProfilingVector();
                auto profileSize = mda.GetProfileSize().value();
                // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
                proto[methodName] = std::vector<uint8_t>(profileData, profileData + profileSize);
            }
        }

        if (!proto.empty()) {
            std::ofstream stm(GetOptions().GetProfileOutput());
            if (!stm.is_open()) {
                LOG(FATAL, RUNTIME) << "Failed to create profile output: " << GetOptions().GetProfileOutput();
            }
            std::vector<uint8_t> buffer;
            auto res = serializer::TypeToBuffer(proto, buffer);
            if (!res) {
                LOG(FATAL, RUNTIME) << "Failed to serialize: " << res.Error();
            }
            stm.write(reinterpret_cast<const char *>(buffer.data()), buffer.size());
            LOG(INFO, RUNTIME) << "Profile with size=" << buffer.size() << " was saved to "
                               << GetOptions().GetProfileOutput();
        }
    }
}

void EcmaVM::UninitializeThreads()
{
    thread_->NativeCodeEnd();
    thread_->CollectTLABMetrics();
    thread_->ClearTLAB();
    thread_->UpdateStatus(ThreadStatus::FINISHED);
}

void EcmaVM::CleanUpTask(Method *method)
{
    static_cast<EcmaRuntimeInterface *>(GetCompilerRuntimeInterface())->CleanFunction(method);
}

EcmaVM::~EcmaVM()
{
    vmInitialized_ = false;
    ClearNativeMethodsData();

    if (ark::plugins::RuntimeTypeToLang(Runtime::GetCurrent()->GetRuntimeType()) !=
        panda_file::SourceLang::ECMASCRIPT) {
        // If ecmavm is not main, it will be created and deleted via speacial api, so we need cleanup classlinker.
        Runtime::GetCurrent()->GetClassLinker()->ResetExtension(panda_file::SourceLang::ECMASCRIPT);
    }

    if (runtimeStat_ != nullptr && runtimeStatEnabled_) {
        runtimeStat_->Print();
    }

    // clear c_address: c++ pointer delete
    ClearBufferData();

    delete regExpParserCache_;
    regExpParserCache_ = nullptr;

    if (stringTable_ != nullptr) {
        delete stringTable_;
        stringTable_ = nullptr;
    }

    if (moduleManager_ != nullptr) {
        delete moduleManager_;
        moduleManager_ = nullptr;
    }

    if (thread_ != nullptr) {
        thread_->DestroyInternalResources();
        delete thread_;
        thread_ = nullptr;
    }

    extractorCache_.clear();
    frameworkProgramMethods_.clear();

    mem::InternalAllocatorPtr allocator = mm_->GetHeapManager()->GetInternalAllocator();
    allocator->Delete(notificationManager_);
    allocator->Delete(factory_);
    allocator->Delete(runtimeStat_);
    allocator->Delete(rendezvous_);
    allocator->Delete(threadManager_);
    allocator->Delete(runtimeIface_);
    allocator->Delete(compiler_);
    if (callProfilingTable_ != nullptr) {
        allocator->Delete(callProfilingTable_);
    }

    mm_->Finalize();
    mem::MemoryManager::Destroy(mm_);
}

bool EcmaVM::ExecuteFromPf(std::string_view filename, std::string_view entryPoint, const std::vector<std::string> &args,
                           bool isModule)
{
    std::unique_ptr<const panda_file::File> pf;
    if (frameworkPandaFile_ == nullptr || !IsFrameworkPandaFile(filename)) {
        pf = panda_file::OpenPandaFileOrZip(filename, panda_file::File::READ_WRITE);
        if (pf == nullptr) {
            return false;
        }
        AddPandaFile(pf.get(), isModule);  // Store here prevent from being automatically cleared
        notificationManager_->LoadModuleEvent(pf->GetFilename());
    } else {
        pf.reset(frameworkPandaFile_);
    }

    return Execute(std::move(pf), entryPoint, args, isModule);
}

bool EcmaVM::ExecuteFromBuffer(const void *buffer, size_t size, std::string_view entryPoint,
                               const std::vector<std::string> &args)
{
    auto pf = panda_file::OpenPandaFileFromMemory(buffer, size);
    if (pf == nullptr) {
        return false;
    }
    AddPandaFile(pf.get(), false);  // Store here prevent from being automatically cleared
    notificationManager_->LoadModuleEvent(pf->GetFilename());

    return Execute(std::move(pf), entryPoint, args);
}

void EcmaVM::DumpHeap(PandaOStringStream *oStr) const
{
    size_t objCnt = 0;
    *oStr << "Dumping heap" << std::endl;
    GetHeapManager()->IterateOverObjects([&objCnt, &oStr](ObjectHeader *mem) {
        JSTaggedValue(mem).Dump(nullptr, *oStr);
        objCnt++;
    });
    *oStr << "\nTotal dumped " << objCnt << std::endl;
}

PandaString EcmaVM::GetClassesFootprint() const
{
    PandaUnorderedMap<JSType, size_t> classesFootprint;
    PandaStringStream result;
    GetHeapManager()->IterateOverObjects([&classesFootprint](ObjectHeader *mem) {
        auto objType = JSTaggedValue(mem).GetTaggedObject()->GetClass()->GetObjectType();
        classesFootprint[objType] += mem->ObjectSize();
    });
    for (const auto &it : classesFootprint) {
        result << "class: " << JSHClass::DumpJSType(it.first) << ", footprint - " << it.second << std::endl;
    }
    return result.str();
}

tooling::ecmascript::PtJSExtractor *EcmaVM::GetDebugInfoExtractor(const panda_file::File *file)
{
    tooling::ecmascript::PtJSExtractor *res = nullptr;
    auto it = extractorCache_.find(file);
    if (it == extractorCache_.end()) {
        auto extractor = std::make_unique<tooling::ecmascript::PtJSExtractor>(file);
        res = extractor.get();
        extractorCache_[file] = std::move(extractor);
    } else {
        res = it->second.get();
    }
    return res;
}

bool EcmaVM::Execute(std::unique_ptr<const panda_file::File> pf, std::string_view entryPoint,
                     const std::vector<std::string> &args, bool isModule)
{
    if (pf == nullptr) {
        return false;
    }

    const panda_file::File *pfPtr = pf.get();
    Runtime::GetCurrent()->GetClassLinker()->AddPandaFile(std::move(pf));
    // Get ClassName and MethodName
    size_t pos = entryPoint.find_last_of("::");
    if (pos == std::string_view::npos) {
        LOG_ECMA(ERROR) << "EntryPoint:" << entryPoint << " is illegal";
        return false;
    }
    PandaString methodName(entryPoint.substr(pos + 1));

    // For Ark application startup
    if (!isModule) {
        thread_->ManagedCodeBegin();
    }
    InvokeEcmaEntrypoint(*pfPtr, methodName, args);
    if (!isModule) {
        thread_->ManagedCodeEnd();
    }
    return true;
}

void EcmaVM::SweepVmRefs(const GCObjectVisitor &gcObjectVisitor)
{
    GetEcmaStringTable()->Sweep(gcObjectVisitor);
    if (HasEcmaCallProfileTable()) {
        GetEcmaCallProfileTable()->Sweep(gcObjectVisitor);
    }
    auto it = finalizationRegistries_.begin();
    while (it != finalizationRegistries_.end()) {
        if (gcObjectVisitor(*it) == ObjectStatus::DEAD_OBJECT) {
            auto toRemove = it++;
            finalizationRegistries_.erase(toRemove);
        } else {
            ++it;
        }
    }
}

JSHandle<GlobalEnv> EcmaVM::GetGlobalEnv() const
{
    return JSHandle<GlobalEnv>(reinterpret_cast<uintptr_t>(&globalEnv_));
}

JSHandle<job::MicroJobQueue> EcmaVM::GetMicroJobQueue() const
{
    return JSHandle<job::MicroJobQueue>(reinterpret_cast<uintptr_t>(&microJobQueue_));
}

Method *EcmaVM::GetNativeMethodWrapper()
{
    if (nativeMethodWrapper_ != nullptr) {
        return nativeMethodWrapper_;
    }
    auto mutf8Name = reinterpret_cast<const uint8_t *>("LEcmascript/Intrinsics;");
    auto klass =
        Runtime::GetCurrent()->GetClassLinker()->GetExtension(panda_file::SourceLang::ECMASCRIPT)->GetClass(mutf8Name);
    if (klass == nullptr) {
        return nullptr;
    }

    mutf8Name = reinterpret_cast<const uint8_t *>("NativeMethodWrapper");

    Method::Proto proto;
    auto &shorty = proto.GetShorty();

    shorty.emplace_back(panda_file::Type::TypeId::VOID);
    shorty.emplace_back(panda_file::Type::TypeId::TAGGED);

    nativeMethodWrapper_ = klass->GetDirectMethod(mutf8Name, proto);
    return nativeMethodWrapper_;
}

JSMethod *EcmaVM::GetMethodForNativeFunction(const void *func)
{
    Method *nWrapper = GetNativeMethodWrapper();
    ASSERT(nWrapper != nullptr);
    auto allocator = Runtime::GetCurrent()->GetInternalAllocator();
    auto method = allocator->New<JSMethod>(nWrapper);
    method->SetNativePointer(const_cast<void *>(func));

    method->SetCompiledEntryPoint(reinterpret_cast<void *>(CompiledCodeToBuiltinBridge));
    nativeMethods_.push_back(method);
    return nativeMethods_.back();
}

void EcmaVM::RedirectMethod(const panda_file::File &pf)
{
    for (auto method : frameworkProgramMethods_) {
        method->SetPandaFile(&pf);
    }
}

void EcmaVM::RegisterFinalizationRegistry(JSHandle<JSFinalizationRegistry> registry)
{
    finalizationRegistries_.push_back(*registry);
}

Expected<int, Runtime::Error> EcmaVM::InvokeEntrypointImpl(Method *entrypoint, const std::vector<std::string> &args)
{
    // For testcase startup
    const panda_file::File *file = entrypoint->GetPandaFile();
    AddPandaFile(file, false);
    ScopedManagedCodeThread managedScope(thread_);
    return InvokeEcmaEntrypoint(*file, utf::Mutf8AsCString(entrypoint->GetName().data), args);
}

Expected<JSTaggedValue, Runtime::Error> EcmaVM::GetInvocableFunction(const panda_file::File &pf,
                                                                     const PandaString &methodName)
{
    [[maybe_unused]] EcmaHandleScope scope(thread_);
    JSHandle<Program> program;
    if (&pf != frameworkPandaFile_) {
        program = PandaFileTranslator::TranslatePandaFile(this, pf, methodName);
    } else {
        JSHandle<EcmaString> string = factory_->NewFromStdStringUnCheck(pf.GetFilename(), true);
        program = JSHandle<Program>(thread_, frameworkProgram_);
        program->SetLocation(thread_, string);
        RedirectMethod(pf);
    }

    SetProgram(*program, &pf);
    if (program.IsEmpty()) {
        LOG_ECMA(ERROR) << "program is empty, invoke entrypoint failed";
        return Unexpected(Runtime::Error::PANDA_FILE_LOAD_ERROR);
    }

    return program->GetMainFunction();
}

Expected<int, Runtime::Error> EcmaVM::InvokeEcmaEntrypoint(const panda_file::File &pf, const PandaString &methodName,
                                                           const std::vector<std::string> &args)
{
    [[maybe_unused]] EcmaHandleScope scope(thread_);
    auto resolveFunc = GetInvocableFunction(pf, methodName);

    if (!resolveFunc.HasValue()) {
        return Unexpected(resolveFunc.Error());
    }

    JSHandle<JSFunction> func(thread_, resolveFunc.Value());
    JSHandle<JSTaggedValue> global = GlobalEnv::Cast(globalEnv_.GetTaggedObject())->GetJSGlobalObject();
    JSHandle<JSTaggedValue> newTarget(thread_, JSTaggedValue::Undefined());
    auto info = NewRuntimeCallInfo(thread_, func, global, newTarget, args.size());
    uint32_t i = 0;
    for (const std::string &str : args) {
        JSHandle<JSTaggedValue> strobj(factory_->NewFromStdString(str));
        info->SetCallArg(i++, strobj.GetTaggedValue());
    }

    JSRuntimeOptions options = this->GetJSOptions();
    InvokeJsFunction(info.Get());

    if (LIKELY(!thread_->HasPendingException())) {
        job::MicroJobQueue::ExecutePendingJob(thread_, GetMicroJobQueue());
    }

    return 0;
}

void EcmaVM::AddPandaFile(const panda_file::File *pf, bool isModule)
{
    ASSERT(pf != nullptr);
    os::memory::LockHolder lock(pandaFileWithProgramLock_);
    pandaFileWithProgram_.push_back(std::make_tuple(nullptr, pf, isModule));
}

#ifndef NDEBUG
static bool IsNonMovable(ObjectHeader *obj, EcmaVM *vm)
{
    mem::GCType gcType = vm->GetGC()->GetType();
    if (gcType == mem::GCType::EPSILON_GC || gcType == mem::GCType::STW_GC) {
        return true;
    }
    return PoolManager::GetMmapMemPool()->GetSpaceTypeForAddr(obj) == SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT;
}
#endif

void EcmaVM::SetProgram(Program *program, const panda_file::File *pf)
{
    ASSERT(IsNonMovable(program, this));
    os::memory::LockHolder lock(pandaFileWithProgramLock_);
    auto it = std::find_if(pandaFileWithProgram_.begin(), pandaFileWithProgram_.end(),
                           [pf](auto entry) { return std::get<1>(entry) == pf; });
    ASSERT(it != pandaFileWithProgram_.end());
    std::get<0>(*it) = program;
}

bool EcmaVM::IsFrameworkPandaFile(std::string_view filename) const
{
    return filename.size() >= frameworkAbcFileName_.size() &&
           filename.substr(filename.size() - frameworkAbcFileName_.size()) == frameworkAbcFileName_;
}

JSHandle<JSTaggedValue> EcmaVM::GetEcmaUncaughtException() const
{
    if (thread_->GetException().IsHole()) {
        return JSHandle<JSTaggedValue>();
    }
    JSHandle<JSTaggedValue> exceptionHandle(thread_, thread_->GetException());
    thread_->ClearException();  // clear for ohos app
    if (exceptionHandle->IsObjectWrapper()) {
        JSHandle<ObjectWrapper> wrapperValue = JSHandle<ObjectWrapper>::Cast(exceptionHandle);
        JSHandle<JSTaggedValue> throwValue(thread_, wrapperValue->GetValue());
        return throwValue;
    }

    return exceptionHandle;
}

void EcmaVM::EnableUserUncaughtErrorHandler()
{
    isUncaughtExceptionRegistered_ = true;
}

void EcmaVM::HandleUncaughtException()
{
    if (isUncaughtExceptionRegistered_) {
        return;
    }
    ScopedManagedCodeThread s(thread_);
    [[maybe_unused]] EcmaHandleScope handleScope(thread_);
    JSHandle<JSTaggedValue> exceptionHandle(thread_, JSTaggedValue(thread_->GetException()));
    // if caught exceptionHandle type is JSError
    thread_->ClearException();
    if (exceptionHandle->IsJSError()) {
        PrintJSErrorInfo(exceptionHandle);
        return;
    }
    if (exceptionHandle->IsObjectWrapper()) {
        JSHandle<ObjectWrapper> wrapperValue = JSHandle<ObjectWrapper>::Cast(exceptionHandle);
        JSHandle<JSTaggedValue> throwValue(thread_, wrapperValue->GetValue());
        if (throwValue->IsJSError()) {
            PrintJSErrorInfo(throwValue);
        } else {
            JSHandle<EcmaString> result = JSTaggedValue::ToString(thread_, throwValue);
            PandaString string = ConvertToPandaString(*result);
            LOG(ERROR, RUNTIME) << string;
        }
    }
}

void EcmaVM::PrintJSErrorInfo(const JSHandle<JSTaggedValue> &exceptionInfo)
{
    JSHandle<JSTaggedValue> nameKey = thread_->GlobalConstants()->GetHandledNameString();
    JSHandle<EcmaString> name(JSObject::GetProperty(thread_, exceptionInfo, nameKey).GetValue());
    JSHandle<JSTaggedValue> msgKey = thread_->GlobalConstants()->GetHandledMessageString();
    JSHandle<EcmaString> msg(JSObject::GetProperty(thread_, exceptionInfo, msgKey).GetValue());
    JSHandle<JSTaggedValue> stackKey = thread_->GlobalConstants()->GetHandledStackString();
    JSHandle<EcmaString> stack(JSObject::GetProperty(thread_, exceptionInfo, stackKey).GetValue());

    PandaString nameBuffer = ConvertToPandaString(*name);
    PandaString msgBuffer = ConvertToPandaString(*msg);
    PandaString stackBuffer = ConvertToPandaString(*stack);
    LOG(ERROR, RUNTIME) << nameBuffer << ": " << msgBuffer << "\n" << stackBuffer;
}

void EcmaVM::ProcessReferences(const WeakRootVisitor &v0)
{
    if (regExpParserCache_ != nullptr) {
        regExpParserCache_->Clear();
    }

    // array buffer
    for (auto iter = arrayBufferDataList_.begin(); iter != arrayBufferDataList_.end();) {
        JSNativePointer *object = *iter;
        auto fwd = v0(reinterpret_cast<TaggedObject *>(object));
        if (fwd == nullptr) {
            object->Destroy();
            iter = arrayBufferDataList_.erase(iter);
        } else if (fwd != reinterpret_cast<TaggedObject *>(object)) {
            *iter = JSNativePointer::Cast(fwd);
            ++iter;
        } else {
            ++iter;
        }
    }
}

void EcmaVM::HandleGCRoutineInMutator()
{
    // Handle references only in JS thread
    ASSERT(Thread::GetCurrent() == thread_);
    ASSERT(GetMutatorLock()->HasLock());
    [[maybe_unused]] EcmaHandleScope scope(thread_);
    for (JSFinalizationRegistry *registry : finalizationRegistries_) {
        JSHandle<JSFinalizationRegistry> handle(thread_, registry);
        JSFinalizationRegistry::CallCleanupCallback(thread_, handle);
    }
}

void EcmaVM::PushToArrayDataList(JSNativePointer *array)
{
    if (std::find(arrayBufferDataList_.begin(), arrayBufferDataList_.end(), array) != arrayBufferDataList_.end()) {
        return;
    }
    arrayBufferDataList_.emplace_back(array);
}

void EcmaVM::RemoveArrayDataList(JSNativePointer *array)
{
    auto iter = std::find(arrayBufferDataList_.begin(), arrayBufferDataList_.end(), array);
    if (iter != arrayBufferDataList_.end()) {
        arrayBufferDataList_.erase(iter);
    }
}

bool EcmaVM::VerifyFilePath(const PandaString &filePath) const
{
    if (filePath.size() > PATH_MAX) {
        return false;
    }

    PandaVector<char> resolvedPath(PATH_MAX);
    auto result = realpath(filePath.c_str(), resolvedPath.data());
    if (result == nullptr) {
        return false;
    }
    std::ifstream file(resolvedPath.data());
    if (!file.good()) {
        return false;
    }
    file.close();
    return true;
}

void EcmaVM::ClearBufferData()
{
    for (auto iter : arrayBufferDataList_) {
        iter->Destroy();
    }
    arrayBufferDataList_.clear();

    os::memory::LockHolder lock(pandaFileWithProgramLock_);
    for (auto iter = pandaFileWithProgram_.begin(); iter != pandaFileWithProgram_.end();) {
        std::get<0>(*iter)->FreeMethodData();
        iter = pandaFileWithProgram_.erase(iter);
    }
    pandaFileWithProgram_.clear();
}

bool EcmaVM::ExecutePromisePendingJob() const
{
    if (LIKELY(!thread_->HasPendingException())) {
        job::MicroJobQueue::ExecutePendingJob(thread_, GetMicroJobQueue());
        return true;
    }
    return false;
}

void *EcmaVM::AllocAndRegisterNative(size_t size)
{
    ScopedNativeCodeThread s(ManagedThread::GetCurrent());
    GetGC()->RegisterNativeAllocation(size);
    void *mem = mm_->GetHeapManager()->GetInternalAllocator()->Alloc(size);
    ASSERT(mem != nullptr);
    return mem;
}

void EcmaVM::FreeAndRegisterNative(void *mem, size_t size)
{
    ASSERT(mem != nullptr);
    GetGC()->RegisterNativeFree(size);
    mm_->GetHeapManager()->GetInternalAllocator()->Free(mem);
}

void EcmaVM::CollectGarbage() const
{
    mm_->GetGC()->WaitForGCInManaged(GCTask(GCTaskCause::EXPLICIT_CAUSE));
}

void EcmaVM::Iterate(const RootVisitor &v)
{
    v(Root::ROOT_VM, ObjectSlot(ToUintPtr(&globalEnv_)));
    v(Root::ROOT_VM, ObjectSlot(ToUintPtr(&microJobQueue_)));
    v(Root::ROOT_VM, ObjectSlot(ToUintPtr(&moduleManager_->ecmaModules_)));
    v(Root::ROOT_VM, ObjectSlot(ToUintPtr(&regexpCache_)));

    os::memory::LockHolder lock(pandaFileWithProgramLock_);
    for (const auto &iter : pandaFileWithProgram_) {
        v(Root::ROOT_VM, ObjectSlot(ToUintPtr(&std::get<0>(iter))));
    }
}

void EcmaVM::SetGlobalEnv(GlobalEnv *global)
{
    ASSERT(global != nullptr);
    globalEnv_ = JSTaggedValue(global);
}

void EcmaVM::SetMicroJobQueue(job::MicroJobQueue *queue)
{
    ASSERT(queue != nullptr);
    microJobQueue_ = JSTaggedValue(queue);
}

const panda_file::File *EcmaVM::GetLastLoadedPandaFile()
{
    os::memory::LockHolder lock(pandaFileWithProgramLock_);
    auto currentFileTuple = pandaFileWithProgram_.back();
    return std::get<1>(currentFileTuple);
}

JSHandle<JSTaggedValue> EcmaVM::GetModuleByName(JSHandle<JSTaggedValue> moduleName)
{
    const std::string &currentPathFile = GetLastLoadedPandaFile()->GetFilename();
    PandaString relativeFile = ConvertToPandaString(EcmaString::Cast(moduleName->GetTaggedObject()));

    // generate full path
    PandaString abcPath = moduleManager_->GenerateModuleFullPath(currentPathFile, relativeFile);

    // Uniform module name
    JSHandle<EcmaString> abcmoduleName = factory_->NewFromString(abcPath);

    JSHandle<JSTaggedValue> module = moduleManager_->GetModule(thread_, JSHandle<JSTaggedValue>::Cast(abcmoduleName));
    if (module->IsUndefined()) {
        PandaString file = ConvertToPandaString(abcmoduleName.GetObject<EcmaString>());
        std::vector<std::string> argv;
        ExecuteModule(file, ENTRY_POINTER, argv);
        module = moduleManager_->GetModule(thread_, JSHandle<JSTaggedValue>::Cast(abcmoduleName));
    }
    return module;
}

void EcmaVM::ExecuteModule(std::string_view moduleFile, std::string_view entryPoint,
                           const std::vector<std::string> &args)
{
    moduleManager_->SetCurrentExportModuleName(moduleFile);
    // Update Current Module
    EcmaVM::ExecuteFromPf(moduleFile, entryPoint, args, true);
    // Restore Current Module
    moduleManager_->RestoreCurrentExportModuleName();
}

void EcmaVM::ClearNativeMethodsData()
{
    auto allocator = Runtime::GetCurrent()->GetInternalAllocator();
    for (auto iter : nativeMethods_) {
        allocator->Delete(iter);
    }
    nativeMethods_.clear();
}

void EcmaVM::SetupRegExpResultCache()
{
    regexpCache_ = builtins::RegExpExecResultCache::CreateCacheTable(thread_);
}

void EcmaVM::HandleLdaStr(Frame *frame, BytecodeId stringId)
{
    auto cp = ConstantPool::Cast(JSFrame::GetJSEnv(frame)->GetConstantPool());
    auto value = cp->GetObjectFromCache(stringId.AsIndex());
    frame->GetAcc().Set(value.GetRawData());
}

coretypes::String *EcmaVM::ResolveString(ConstantPool *cp, panda_file::File::EntityId id)
{
    auto value = cp->GetObjectFromCache(id.GetOffset());
    return coretypes::String::Cast(value.GetHeapObject());
}

coretypes::String *EcmaVM::ResolveString(Frame *frame, panda_file::File::EntityId id)
{
    return EcmaVM::ResolveString(ConstantPool::Cast(JSFrame::GetJSEnv(frame)->GetConstantPool()), id);
}

std::unique_ptr<const panda_file::File> EcmaVM::OpenPandaFile(std::string_view location)
{
    panda_file::File::OpenMode openMode = GetLanguageContext().GetBootPandaFilesOpenMode();
    auto pf = panda_file::OpenPandaFile(location, "", openMode);
    // PandaFileTranslator can trigger GC.
    ScopedManagedCodeThread s(thread_);
    [[maybe_unused]] EcmaHandleScope handleScope(GetJSThread());
    auto program = PandaFileTranslator::TranslatePandaFile(this, *pf, ENTRY_METHOD_POINTER.data());
    {
        os::memory::LockHolder lock(pandaFileWithProgramLock_);
        pandaFileWithProgram_.emplace_back(program.GetObject<Program>(), pf.get(), false);
    }
    return pf;
}

coretypes::String *EcmaVM::GetNonMovableString([[maybe_unused]] const panda_file::File &pf,
                                               [[maybe_unused]] panda_file::File::EntityId id) const
{
    return nullptr;
}

void EcmaVM::HandleReturnFrame() {}

void EcmaVM::VisitVmRoots(const GCRootVisitor &visitor)
{
    PandaVM::VisitVmRoots(visitor);
    ObjectXRay rootManager(this);
    auto commonVisitor = [&visitor](Root type, ObjectSlot slot) {
        mem::RootType root;
        switch (type) {
            case Root::ROOT_FRAME:
                root = mem::RootType::ROOT_FRAME;
                break;
            default:
                root = mem::RootType::ROOT_VM;
                break;
        }
        JSTaggedValue value(slot.GetTaggedType());
        if (value.IsHeapObject()) {
            visitor(mem::GCRoot(root, value.GetHeapObject()));
        }
    };

    auto singleVisitor = [&commonVisitor](Root type, ObjectSlot slot) { commonVisitor(type, slot); };

    auto rangeVisitor = [&commonVisitor](Root type, ObjectSlot start, ObjectSlot end) {
        for (ObjectSlot slot = start; slot < end; slot++) {
            commonVisitor(type, slot);
        }
    };
    rootManager.VisitVMRoots(singleVisitor, rangeVisitor);
    if (HasEcmaCallProfileTable()) {
        GetEcmaCallProfileTable()->VisitRoots(visitor);
    }
}

void EcmaVM::UpdateVmRefs()
{
    PandaVM::UpdateVmRefs();
    ObjectXRay rootManager(this);
    auto singleVisitor = []([[maybe_unused]] Root type, ObjectSlot slot) {
        JSTaggedValue value(slot.GetTaggedType());
        if (value.IsHeapObject()) {
            ObjectHeader *object = value.GetHeapObject();
            if (object->IsForwarded()) {
                slot.Update(TaggedObject::Cast(ark::mem::GetForwardAddress(object)));
            }
        }
    };
    auto singlePtrVisitor = []([[maybe_unused]] Root type, ObjectHeader **object) {
        if ((*object)->IsForwarded()) {
            *object = TaggedObject::Cast(ark::mem::GetForwardAddress(*object));
        }
    };

    auto rangeVisitor = [&singleVisitor](Root type, ObjectSlot start, ObjectSlot end) {
        for (ObjectSlot slot = start; slot < end; slot++) {
            singleVisitor(type, slot);
        }
    };
    rootManager.VisitVMRoots(singleVisitor, rangeVisitor);

    // Special handling of array_buffer_data_list_
    // Don't handle it in ObjectXRay because these refs shouldn't be marked
    // as gc roots
    // NOLINTNEXTLINE(modernize-loop-convert)
    for (size_t i = 0; i < arrayBufferDataList_.size(); ++i) {
        singlePtrVisitor(Root::ROOT_VM, reinterpret_cast<ObjectHeader **>(&arrayBufferDataList_[i]));
    }

    for (auto &entry : finalizationRegistries_) {
        singlePtrVisitor(Root::ROOT_VM, reinterpret_cast<ObjectHeader **>(&entry));
    }

    // Update call profiling table
    if (HasEcmaCallProfileTable()) {
        GetEcmaCallProfileTable()->UpdateMoved();
    }
}

}  // namespace ark::ecmascript
